package ch.myDungeons.game;

import java.util.ArrayList;

import ch.myDungeons.characters.NPC;
import ch.myDungeons.characters.Person;
import ch.myDungeons.characters.Player;
import ch.myDungeons.io.IOStream;
import ch.myDungeons.io.ItemGenerator;
import ch.myDungeons.io.LanguagePack;
import ch.myDungeons.io.MapGenerator;
import ch.myDungeons.io.PersonGenerator;
import ch.myDungeons.game.MyDungeon;
import ch.myDungeons.view.GameFrame;

/**
 * The main game class. Containing all the game objects etc
 * @author Grii
 *
 */

public class Game {
	/** the game window */
	public GameFrame gameFrame;
	
	/** the player */
	public Player player;
	
	/** the current map display of the game */
	public MapDisplay mapDisplay;

	/** the current map of the game */
	public Map map;

	/** the current map level of the game */
	public int mapLevel = 0;

	/** the current map list of the game */
	public ArrayList<Map> maps;
	
	/** number of the current turn */
	public int turnNumber;
	
	/** whether the game is running or finished */
	public boolean running = false;
	
	/** true if the player won the game */
	boolean playerWon = false;
	
	/** true if the player wants to start a new game */
	boolean playAgain = false;
	
	
	/** Constructor only creates the map and the window */
	public Game() {
		gameFrame = new GameFrame(1200, 700);
	}
	
	
	/** Creates all Characters and Objects on the map */
	public void setupCharactersEtc() {
		mapLevel = 0;
		maps = new ArrayList<Map>();
		map = MapGenerator.createMapWithRooms(50, 20, 4, 3, 6, 1);
		map = MapGenerator.addSomeItems(map);
		maps.add(map);
		mapDisplay = new MapDisplay(map, gameFrame.getMapView());
		gameFrame.getMapView().setMapDisplay(mapDisplay);
		
		gameFrame.getMapView().clearMap();
		gameFrame.getConsole().clearOutput();
		playAgain = false;
		turnNumber = 0;
		running = false;
		playerWon = false;
		
		// Player setup
		String playerName = "";
		gameFrame.getConsole().consoleInput.requestFocus();
		gameFrame.getConsole().println(LanguagePack.get("whats_your_name"));
		playerName = gameFrame.getConsole().getNormalInput();
		if (playerName.isEmpty() || playerName.equals("") || playerName.matches("\\s*")) {
			playerName = LanguagePack.get("the_player");
		}
		if (IOStream.checkCharacterName(playerName)) {
			gameFrame.getConsole().println(LanguagePack.get("loading_game"));
			restoreGameFromSave(playerName);
		} else {
			player = new Player(playerName);
			player.addAndEquipWeapon(ItemGenerator.createWeapon(0));
			player.getInventory().addItem(ItemGenerator.createPotion(1 + (int)(Math.random()*3)));
			placePlayer(map.getStairs()[2], map.getStairs()[3]);
			placeCharacters();
		}
	}
	
	/** used to start the game and run it */
	public void runGame(){
		while (running) {
			makeTurn();
		}
		running = false;
		while (!playAgain) {
			afterTheGame();
		}
		player = null;
		MyDungeon.restartGame();
	}
	
	/** Place the player etc on the map */
	public void placeCharacters(){
		int spawnedCharactersLevel = (mapLevel+1 + player.getLevel()) / 2;
		int enemyCount = 10;
		map.enemies = new ArrayList<NPC>();
		for (int i = 0; i < enemyCount; i++) {
			NPC tmpEnemy = ch.myDungeons.io.PersonGenerator.createEnemy(spawnedCharactersLevel-1 + (int)(Math.random()*3));
			while(!tmpEnemy.placeOnMap((int)(Math.random()*map.tiles.length), (int)(Math.random()*map.tiles[0].length), map, gameFrame.getConsole()));
			map.enemies.add(tmpEnemy);
		}
		int alliesCount = 8;
		map.allies = new ArrayList<NPC>();
		for (int i = 0; i < alliesCount; i++) {
			NPC tmpAlly = ch.myDungeons.io.PersonGenerator.createAlly(spawnedCharactersLevel-1 + (int)(Math.random()*3));
			while(!tmpAlly.placeOnMap((int)(Math.random()*map.tiles.length), (int)(Math.random()*map.tiles[0].length), map, gameFrame.getConsole()));
			map.allies.add(tmpAlly);
		}
	}
	
	/** Place the Player on the map */
	public void placePlayer(int posX, int posY){
		if (!player.placeOnMap(posX, posY, map, gameFrame.getConsole())) { // try to place the player on the target tile
			while(!player.placeOnMap((int)(Math.random()*map.tiles.length), (int)(Math.random()*map.tiles[0].length), map, gameFrame.getConsole())); // currently the player starts on a random field
		}
		
	}
	
	
	/** Change the current level */
	public void changeLevel(boolean stairsUp){
		if (stairsUp) {
			if (mapLevel == 0) {
				gameFrame.getConsole().println(LanguagePack.get("you_cannot_leave_the_dungeon_yet"));
			} else {
				mapLevel--;
				loadMap(true);
			}
		} else {
			if (maps.size() > mapLevel+1) {
				mapLevel++;
				loadMap(false);
			} else {
				map.tiles[player.getPosX()][player.getPosY()].personOnTheTile = null;
				mapLevel++;
				map = MapGenerator.createMapWithRooms(50, 20, 4, 3, 6, 1);
				map = MapGenerator.addSomeItems(map);
				maps.add(map);
				mapDisplay.changeMap(map);
				placePlayer(map.getStairs()[2], map.getStairs()[3]);
				placeCharacters();
			}
		}
	}
	
	/** Load another map level */
	private void loadMap(boolean onStairsDown){
		map.tiles[player.getPosX()][player.getPosY()].personOnTheTile = null;
		map = maps.get(mapLevel);
		mapDisplay.changeMap(map);
		if (onStairsDown) {
			placePlayer(map.getStairs()[0], map.getStairs()[1]);
		} else {
			placePlayer(map.getStairs()[2], map.getStairs()[3]);
		}
		
	}
	
	/** Do some stuff before the first turn begins */
	public void makeFirstTurn() {
		if (turnNumber < 1) {
			turnNumber = 1;
			gameFrame.getConsole().println(LanguagePack.get("a_new_game_is_about_to_start"));
		}
		running = true;
		gameFrame.getConsole().println(LanguagePack.get("starts_on"));
		for (int i = 0; i < map.enemies.size(); i++) {
			gameFrame.getConsole().println(map.enemies.get(i).getNameWithPrefix() + " " + LanguagePack.get("starts_on") + " (" + map.enemies.get(i).getPosX() + ", " + map.enemies.get(i).getPosY() + ")");
		}
		for (int i = 0; i < map.allies.size(); i++) {
			gameFrame.getConsole().println(map.allies.get(i).getNameWithPrefix() + " " + LanguagePack.get("starts_on") + " (" + map.allies.get(i).getPosX() + ", " + map.allies.get(i).getPosY() + ")");
		}
		gameFrame.getConsole().println("");
		makeTurn();
	}
	
	/** Do everything that's required every turn */
	public void makeTurn() {
		muhCounter = 0;
		
		gameFrame.getConsole().println("");
		gameFrame.getConsole().println(LanguagePack.get("turn") + " " + turnNumber);
		
		mapDisplay.printMap();
		gameFrame.getInfoView().updateInfo(player.getInfo());
		
		String command = gameFrame.getConsole().getInput().toLowerCase();
		while(!checkCommand(command)){
			mapDisplay.printMap();
			gameFrame.getInfoView().updateInfo(player.getInfo());
			command = gameFrame.getConsole().getInput().toLowerCase();
		}
		
		for (int i = 0; i < map.enemies.size(); i++) {
			map.enemies.get(i).doTurn();
		}
		for (int i = 0; i < map.allies.size(); i++) {
			map.allies.get(i).doTurn();
		}
		turnNumber++;
		
	}
	
	/** remove a person (ally, enemy or player) from the game after he died */
	public void removePerson(Person person){
		if(person == player){
			gameFrame.getConsole().println(LanguagePack.get("the_player_is_dead"));
			running = false;
		} else {
			if (person.isFriendly()) {
				map.allies.remove(person);
			} else {
				map.enemies.remove(person);
			}
		}
	}
	
	/** add a person (ally, enemy or player) to the game */
	public void addPerson(NPC person){
		if (person.isFriendly()) {
			map.allies.add(person);
		} else {
			map.enemies.add(person);
		}
	}
	
	/** Checks and executes valid commands */
	boolean checkCommand(String command){
		if (executeInternalCommand(command));
		else if (player.doTurn(command));
		else {
			return false;
		}
		return true;
	}
	
	
	private int muhCounter = 0;
	/** Checks for and executes internal commands */
	boolean executeInternalCommand (String command) {
		if (command.equals("exit")) {
			gameFrame.getConsole().println(LanguagePack.get("really_exit") + " (y/n)");
			String answer = gameFrame.getConsole().getNormalInput();
			if (answer.equals("y")) {
				System.exit(0);
			} else {
				return false;
			}
		} else if (command.equals("newgame") || command.equals("restart")) {
			gameFrame.getConsole().println(LanguagePack.get("really_start_a_new_game") + " (y/n)");
			String answer = gameFrame.getConsole().getNormalInput();
			if (answer.equals("y")) {
				running = false;
				turnNumber = 0;
				playAgain = true;
			} else {
				return false;
			}
		} else if (command.equals("save")) {
			gameFrame.getConsole().println(LanguagePack.get("really_save_game") + " (y/n)");
			String answer = gameFrame.getConsole().getNormalInput();
			if (answer.equals("y")) {
				saveGame();
			}
			return false;
		} else if (command.equals("load")) {
			gameFrame.getConsole().println(LanguagePack.get("really_load_game") + " (y/n)");
			String answer = gameFrame.getConsole().getNormalInput();
			if (answer.equals("y")) {
				restoreGameFromSave(player.getName());
			}
			return false;
		} else if (command.equals("stairs")) {
			int[] stairsLoc = map.getStairs();
			gameFrame.getConsole().println(LanguagePack.get("stairs_up") + ": " + stairsLoc[2] + "/" + stairsLoc[3]);
			gameFrame.getConsole().println(LanguagePack.get("stairs_down") + ": " + stairsLoc[0] + "/" + stairsLoc[1]);
			return false;
		} else if (command.equals("help") || command.equals("?") ) {
			gameFrame.getConsole().println(IOStream.helpFile);	
			
			return false;
		} else if (command.equals("moo") || command.equals("muh") ) {
			muhCounter++;
			switch (muhCounter) {
			case 1:
				gameFrame.getConsole().println("muh?");
				break;
			case 2:
				gameFrame.getConsole().println("moo?");
				break;
			case 3:
				gameFrame.getConsole().println("muuuuh?");
				break;
			case 4:
				gameFrame.getConsole().println("moooooo?");
				break;
			case 5:
				gameFrame.getConsole().println("MUUUUUUUH!!!");
				gameFrame.getConsole().println(LanguagePack.get("you_awoke_the_spirit_of_the_cow"));
				NPC cowGod = PersonGenerator.createCowBoss(player.getLevel());
				while(!cowGod.placeOnMap((int)(Math.random()*map.tiles.length), (int)(Math.random()*map.tiles[0].length), map, gameFrame.getConsole()));
				addPerson(cowGod);
				break;
			}
			return false;
		} else if (command.matches("cheat_spawn\\s\\w+")) {
			String personName = command.split("\\s")[1].replaceAll("_", " ");
			NPC person = PersonGenerator.createPersonFromName(personName, 1, true);
			if (person != null) {
				while(!person.placeOnMap((int)(Math.random()*map.tiles.length), (int)(Math.random()*map.tiles[0].length), map, gameFrame.getConsole()));
				addPerson(person);
			}
			return false;
		} else {
			return false;
		}
		return true;
	}
	
	/** Executed after the game terminated */
	void afterTheGame(){
		mapDisplay.printMap();
		if (playerWon) {
			gameFrame.getInfoView().updateInfo(LanguagePack.get("you_win_the_game"));
		} else {
			gameFrame.getInfoView().updateInfo(LanguagePack.get("you_lose_the_game"));
		}
		String command = gameFrame.getConsole().getInput().toLowerCase();
		while(!executeInternalCommand(command)){
			command = gameFrame.getConsole().getInput().toLowerCase();
		}
	}
	
	/** Loads a game */
	public void restoreGameFromSave(String name){
		GameData loadedGameData =  IOStream.load(name);
		if(loadedGameData != null){
			mapLevel = loadedGameData.getMapLevel();
			turnNumber = loadedGameData.getTurnNumber();
			maps = new ArrayList<Map>();
			for (int i = 0; i < loadedGameData.getMaps().length; i++) {
				maps.add(loadedGameData.getMaps()[i]);
			}
			
			map = maps.get(mapLevel);
			mapDisplay.changeMap(map);
			
			for (int i = 0; i < map.tiles.length; i++) {
				for (int j = 0; j < map.tiles[0].length; j++) {
					if (map.tiles[i][j].personOnTheTile != null && map.tiles[i][j].personOnTheTile instanceof Player) {
						player = (Player)map.tiles[i][j].personOnTheTile;
					}
				}
			}
			
			gameFrame.getConsole().println(LanguagePack.get("welcome_back") + " " + LanguagePack.capitalizeString(name));
		} else {
			gameFrame.getConsole().println(LanguagePack.get("couldnt_load_game"));
		}
	}
	
	/** Saves a game */
	public void saveGame(){
		Map[] mapArray = new Map[maps.size()];
		for (int i = 0; i < mapArray.length; i++) {
			mapArray[i] = maps.get(i);
		}
		gameFrame.getConsole().println(LanguagePack.get("saving_game"));
		if (IOStream.save(new GameData(player.getName(), mapArray, mapLevel, turnNumber))) {
			gameFrame.getConsole().println(LanguagePack.get("game_saved"));
		} else {
			gameFrame.getConsole().println(LanguagePack.get("couldnt_save_game"));
		}
	}


	public void setPlayer(Player player) {
		this.player = player;
	}
}
